package sync

import (
	"sync"
	"time"

	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/module/socket"
	"gddgame.cc/galaxy/common/feature/orbit"
	"gddgame.cc/galaxy/common/feature/orbit/sync_protocol"
	"gddgame.cc/galaxy/satellite/formula/comet"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/serialize"
)

var (
	clientPool = sync.Pool{}
)

func init() {
	clientPool.New = func() interface{} {
		c := &Client{
			// 最大长度要小于一个Mut：200字节，也就是一条数据15-20字节
			InstanceBatch: *orbit.NewBatch(12),
			DestroyBatch:  *orbit.NewBatch(40),
			QueueBatch:    *orbit.NewBatch(25),
		}
		c.init()
		return c
	}
}

type ModeService interface {
	ID() string
	NeedBlock() bool
	GetNodeId(area string, block *orbit.BlockLocation) string
	GetWithLocation(c *Client, container cluster.Container, locationByte []byte) *cluster.Client
	GetWithBlock(c *Client, container cluster.Container, block *orbit.BlockLocation) *cluster.Client
	GetWithNodeId(container cluster.Container, nodeId string) *cluster.Client
	UpdateLocation(c *Client, location *orbit.Location)
	binocle.Service
}

type ModeManager interface {
	SwitchMode(mode sync_protocol.SyncMode) ModeService
}

type Client struct {
	*comet.Socket
	Manager ModeManager

	Region         string
	OpenId         string
	Gap            orbit.Location
	MaxVision      orbit.Location
	FrameRate      time.Duration
	UpdateLocation bool

	Area    string
	Vision  orbit.Location
	Target  uint32
	Service ModeService

	// 是否需要获取data更新
	DataMap sync.Map // map[int64]bool

	// 是否有操作权：代理权或者观察权，对应的存放block
	BlockMap sync.Map // map[int64]*cluster.Client
	// 是否拥有代理权，对应存放坐标AreaBlock
	AgentMap sync.Map // map[int64]*AreaBlock, 需要判断是否为空
	// 当前链接的Block节点
	BlockSet map[string]*cluster.Client

	// 距离限制
	ItemRateLimit sync.Map // map[int64]int8

	// 当前location
	Location  *orbit.Location
	Direction orbit.Direction

	// single
	// 全局Client
	CurrentClient *cluster.Client

	// area，cluster模式
	NearBlock []*orbit.BlockLocation

	QueueBatch    orbit.Batch
	InstanceBatch orbit.Batch
	DestroyBatch  orbit.Batch
}

func NewClient() *Client {
	c := clientPool.Get().(*Client)

	c.OpenId = ""
	c.Area = ""
	c.Target = 0
	c.Vision = orbit.Location{}
	c.Gap = orbit.Location{}
	c.MaxVision = orbit.Location{}
	return c
}
func (c *Client) Bind(socket *comet.Socket) {
	c.Socket = socket
}
func (c *Client) init() {
	c.BlockSet = make(map[string]*cluster.Client, 4)
	c.CurrentClient = nil
	c.AgentMap = sync.Map{}
	c.BlockMap = sync.Map{}
	if c.NearBlock == nil {
		c.NearBlock = make([]*orbit.BlockLocation, 0, 4)
	} else {
		c.NearBlock = c.NearBlock[0:0]
	}
	c.InstanceBatch.Init()
	c.DestroyBatch.Init()
	c.QueueBatch.Init()
}

func (c *Client) Release() {
	c.Socket = nil
	c.init()
	clientPool.Put(c)
}

func (c *Client) Reset(destroy bool) {
	// 销毁所有，移除所有
	clientMap := make(map[string]*cluster.Client)
	if c.CurrentClient != nil {
		clientMap[c.CurrentClient.ID()] = c.CurrentClient
	}
	if len(c.BlockSet) > 0 {
		for id, client := range c.BlockSet {
			if _, ok := clientMap[id]; !ok {
				clientMap[id] = client
			}
		}
	}
	b := serialize.GetBag()
	c.BlockMap.Range(func(key, value interface{}) bool {
		client := value.(*cluster.Client)
		id := client.ID()
		if _, ok := clientMap[id]; !ok {
			clientMap[id] = client
		}
		if destroy {
			if _, ok := c.AgentMap.Load(id); ok {
				b.Write(0, key)
				client.Emit().Origin(c).Data(b.Bytes()).Send(sync_protocol.Destroy)
			}
		}
		return true
	})
	c.AgentMap.Range(func(key, value interface{}) bool {
		if value == nil {
			return true
		}
		block := value.(*orbit.BlockLocation)
		orbit.LocationCM.ReleaseBlock(block)
		return true
	})
	c.BlockMap = sync.Map{}
	b.Release()
	for _, client := range clientMap {
		if client != nil {
			client.Leave(c, c.OpenId)
		}
	}
	if !destroy && c.Location != nil {
		// 断线离开, 提交坐标数据
		_ = c.Service.Request(c.Region).Hash().Handler(func(res socket.Response, err error) {
			res.Release()
		}).Do("area/location", &sync_protocol.AreaUpdateLocation{
			OpenId:   c.OpenId,
			AreaId:   c.Area,
			Location: orbit.LocationCM.EncodeLocation(c.Location),
		})
	}
	c.ToUpdateLocation(nil)
	c.InstanceBatch.Clear()
	c.DestroyBatch.Clear()
	c.QueueBatch.Clear()
	c.DataMap = sync.Map{}
	c.Area = ""
	c.Target = 0
}

func (c *Client) GetFrameRate() time.Duration {
	return c.FrameRate
}

func (c *Client) Dispatch(container cluster.Container, event comet.DE, packet comet.Packet) {
	switch event {
	case sync_protocol.Switch:
		// [area string, mode byte, vision int]
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		config := sync_protocol.AreaConfig{}
		if b.Len() < 5 {
			config.Area = b.ReadString(0)
			if b.Len() > 1 {
				mode := b.ReadByte(1)
				config.SyncMode = sync_protocol.SyncMode(mode)
				if b.Len() > 2 {
					config.MaxVision = b.ReadInt32(2)
				}
			}
		} else {
			_, _ = config.UnmarshalMsg(packet.ReadBytes())
		}

		b.Release()
		c.Debugf("Client Switch:%s", config.Area)
		c.Send(event, nil)
	case sync_protocol.Setting:
		c.ProcessSetting(packet, container)
	case sync_protocol.Instance:
		// [localid byte, location []byte, level byte, meta []interface{}, info []interface{}, data []interface{}, ownerId string, belong string],
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		mode := c.Service
		for k, _ := range b.Data {
			row, ok := b.Data[k].([]interface{})
			if !ok {
				continue
			}
			one := serialize.GetBag()
			one.SetList(row)
			one.Push(c.OpenId)
			client := mode.GetWithLocation(c, container, one.ReadBytes(1))
			if _, ok := c.BlockSet[client.ID()]; !ok {
				client.Join(c, c.OpenId)
				c.BlockSet[client.ID()] = client
			}
			// version int, assign string
			client.Emit().Origin(c).Data(one).Send(sync_protocol.Instance)
			one.Release()
		}
		b.Release()
	case sync_protocol.Destroy:
		// hash int64[]
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}

		for k, _ := range b.Data {
			hash := utils.ToUint32(b.Data[k])
			if block, ok := c.AgentMap.Load(hash); ok {
				value, _ := c.BlockMap.Load(hash)
				client := value.(*cluster.Client)
				client.Emit().Origin(c).Data(b).Send(sync_protocol.Destroy)
				c.BlockMap.Delete(hash)
				if block != nil {
					orbit.LocationCM.ReleaseBlock(block.(*orbit.BlockLocation))
				}
				c.AgentMap.Delete(hash)
			}
		}
		b.Release()
	case sync_protocol.Queue:
		// [hash int64, type byte, localVersion, xx]
		// Info: [location []byte, level byte, info []interface{}]
		// Message: [code byte, data []byte, ping byte]
		// Data: [index[]byte, data []interface{}]
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		mode := c.Service
		// 调整对象的block，更新Location关联block列表
		for k, _ := range b.Data {
			row, ok := b.Data[k].([]interface{})
			if !ok {
				continue
			}
			one := serialize.GetBag()
			one.SetList(row)
			hash := one.ReadUint32(0)
			if value, ok := c.BlockMap.Load(hash); ok {
				var location *orbit.Location
				var client *cluster.Client
				var block *orbit.BlockLocation
				t := one.ReadByte(1)
				if t&sync_protocol.InfoQueue > 0 {
					tmpLocation := one.ReadBytes(1)
					location = orbit.LocationCM.DecodeLocation(tmpLocation)
				}
				if value != nil {
					client = value.(*cluster.Client)
				} else {
					client = nil
				}
				v, b := c.AgentMap.Load(hash)
				if b && v != nil {
					block = v.(*orbit.BlockLocation)
				} else {
					block = nil
				}
				flag := false
				if block == nil {
					block = orbit.LocationCM.CenterBlockWithLocation(c.Gap, orbit.Location{
						X: c.Gap.X + c.MaxVision.X,
						Y: c.Gap.Y + c.MaxVision.Y,
					}, location)
					flag = true
				}
				if client == nil {
					client = mode.GetWithBlock(c, container, block)
				}
				one.Alloc()
				client.Emit().Origin(c).Data(one).Send(sync_protocol.Queue)
				client.Broadcast().Origin(c).Data(one).Send(sync_protocol.Queue)

				if location != nil && !block.Contain(location) {
					flag = true
					nextBlock := orbit.LocationCM.CenterBlockWithLocation(c.Gap, orbit.Location{
						X: c.Gap.X + c.MaxVision.X,
						Y: c.Gap.Y + c.MaxVision.Y,
					}, location)
					nextClient := mode.GetWithBlock(c, container, nextBlock)

					// 发送转移命令
					client.Emit().Origin(c).List(hash, nextClient.ID()).Send("transfer")
					client = nextClient
					c.BlockMap.Store(hash, client)
					// block需要手动释放
					orbit.LocationCM.ReleaseBlock(block)
					block = nextBlock
				}
				if flag {
					c.AgentMap.Store(hash, block)
				}
				if c.Target == hash && location != nil {
					c.NearbyBlock(mode, container, block, location)
				}
			}
			one.Release()
		}
		b.Release()
	case sync_protocol.Apply:
		// hash int64, type byte, want string
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		hash := b.ReadUint32(0)
		value, ok := c.BlockMap.Load(hash)
		// 如果未获取，则以当前目标client通信
		if !ok && c.Target != 0 {
			value, ok = c.BlockMap.Load(c.Target)
		}
		if ok {
			client := value.(*cluster.Client)
			b.Push(c.OpenId)
			client.Emit().Origin(c).Data(b).Send(event)
		}
		b.Release()
	case sync_protocol.Broadcast:
		// location []byte, code byte, value []byte
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		mode := c.Service
		b.Push(c.OpenId)
		client := mode.GetWithLocation(c, container, b.ReadBytes(0))
		client.Broadcast().Origin(c).Data(b).Send(sync_protocol.Broadcast)
		b.Release()
	case sync_protocol.Time:
		// [code byte location []byte [info]]
		b := serialize.GetBag()
		if err := b.Unmarshal(packet.ReadBytes()); err != nil {
			b.Release()
			return
		}
		mode := c.Service
		for k, _ := range b.Data {
			row, ok := b.Data[k].([]interface{})
			if !ok {
				continue
			}
			one := serialize.GetBag()
			one.SetList(row)
			client := mode.GetWithLocation(c, container, one.ReadBytes(1))
			client.Emit().Origin(c).Data(one).Send(sync_protocol.Time)
			one.Release()
		}
		b.Release()
	}
}

func (c *Client) ProcessSetting(packet comet.Packet, container cluster.Container) {
	// settingType byte
	b := serialize.GetBag()
	if err := b.Unmarshal(packet.ReadBytes()); err != nil {
		b.Release()
		return
	}
	settingType := b.ReadByte(0)
	switch sync_protocol.SettingType(settingType) {
	case sync_protocol.BindSettingType:
		// 绑定hash作为同步中心
		// settingType byte, hash int64
		hash := b.ReadUint32(1)
		if c.Target != hash {
			if _, ok := c.AgentMap.Load(hash); ok {
				c.Target = hash
			}
		}
	case sync_protocol.DataSettingType:
		// Data同步流程：开启对于hash的data接受
		// settingType byte, hash int64
		// fmt.Println("Data setting:", hash)
		hash := b.ReadUint32(1)
		c.DataMap.LoadOrStore(hash, true)
	case sync_protocol.VisionSettingType:
		// 设置当前角色的视线范围：用来控制变速同步info
		// settingType byte, vision []byte
		vision := b.ReadInt32(1)
		c.Vision = orbit.Location{
			X: orbit.Point(vision),
			Y: orbit.Point(vision),
		}
	case sync_protocol.GapSettingType:
		// 获取Gap信息
		bb := serialize.GetBag()
		bb.Set(settingType, c.Gap)
		c.SendData(sync_protocol.Setting, bb)
		bb.Release()
	case sync_protocol.LocationSettingType:
		// 设置定位点
		// settingType byte, location []byte
		location := orbit.LocationCM.DecodeLocation(b.ReadBytes(1))
		block := orbit.LocationCM.CenterBlockWithLocation(c.Gap, orbit.Location{
			X: c.Gap.X + c.MaxVision.X,
			Y: c.Gap.Y + c.MaxVision.Y,
		}, location)
		c.NearbyBlock(c.Service, container, block, location)
		orbit.LocationCM.ReleaseBlock(block)
	}
	b.Release()
}

func (c *Client) UpdateConfig(config *sync_protocol.AreaConfig, container cluster.Container) {
	if config.SyncMode > 0 {
		c.Service = c.Manager.SwitchMode(config.SyncMode)
	}
	if config.MaxVision > 0 {
		c.MaxVision = orbit.Location{
			X: orbit.Point(config.MaxVision),
			Y: orbit.Point(config.MaxVision),
		}
	}
	if config.Gap > 0 {
		c.Gap = orbit.Location{
			X: orbit.Point(config.Gap),
			Y: orbit.Point(config.Gap),
		}
	}
	if config.Rate > 0 {
		c.FrameRate = time.Duration(utils.ToInt64(config.Rate)) * time.Millisecond // 毫秒转纳秒
	}
	c.UpdateLocation = config.UpdateLocation

	if c.Area != config.Area {
		// 替换切换area
		if c.Area != "" {
			c.Reset(true)
		}
		c.Area = config.Area

		if config.Area != "" {
			client := c.Service.GetWithBlock(c, container, nil)
			if client != nil {
				c.CurrentClient = client
				c.CurrentClient.Join(c, c.OpenId)
			}
		}
	}
}

func (c *Client) SendSetting(settingType sync_protocol.SettingType, data ...interface{}) {
	bb := serialize.GetBag()
	data = append([]interface{}{settingType}, data...)
	bb.SetList(data)
	c.SendData(sync_protocol.Setting, bb)
	bb.Release()
}

func (c *Client) ToUpdateLocation(location *orbit.Location) {
	if c.Location != nil {
		orbit.LocationCM.ReleaseLocation(c.Location)
	}
	c.Location = location
	if c.Location != nil && c.Service != nil {
		c.Service.UpdateLocation(c, location)
	}
}

func (c *Client) WriteInstance(data []interface{}) {
	c.InstanceBatch.Write(c, sync_protocol.Instance, data)
}

func (c *Client) WriteDestroy(data []interface{}) {
	c.DestroyBatch.Write(c, sync_protocol.Destroy, data)
}

func (c *Client) WriteQueue(data []interface{}) {
	c.QueueBatch.Write(c, sync_protocol.Queue, data)
	// 将缓冲清空
	if c.InstanceBatch.Index >= 0 {
		c.InstanceBatch.Flush(c, sync_protocol.Instance, false)
	}
	if c.DestroyBatch.Index >= 0 {
		c.DestroyBatch.Flush(c, sync_protocol.Destroy, false)
	}
}

func (c *Client) LocationToBlock(locationByte []byte) *orbit.BlockLocation {
	location := orbit.LocationCM.DecodeLocation(locationByte)
	block := orbit.LocationCM.CenterBlockWithLocation(c.Gap, orbit.Location{
		X: c.Gap.X + c.MaxVision.X,
		Y: c.Gap.Y + c.MaxVision.Y,
	}, location)
	orbit.LocationCM.ReleaseLocation(location)
	return block
}

// 0不限制
// > 0 表示几次转发一次
// < 0 表示不同步
func (c *Client) InfoRate(tmpLocation []byte) int8 {
	if c.Location == nil {
		return 0
	}
	if c.Vision.X == 0 {
		return 0
	}
	location := orbit.LocationCM.DecodeLocation(tmpLocation)
	x := c.Location.X - location.X
	if x < 0 {
		x = orbit.Point(uint32(x) &^ (1 << 31))
	}
	y := c.Location.Y - location.Y
	if y < 0 {
		y = orbit.Point(uint32(y) &^ (1 << 31))
	}
	orbit.LocationCM.ReleaseLocation(location)
	if x > c.Vision.X {
		return -1
	}
	if y > c.Vision.Y {
		return -1
	}
	if x < c.Vision.X/2 && y < c.Vision.Y/2 {
		return 0
	}
	return 10 - int8((c.Vision.X-x+c.Vision.Y-y)*10/(c.Vision.X+c.Vision.Y))
}

func (c *Client) NearbyBlock(mode ModeService, container cluster.Container, block *orbit.BlockLocation, location *orbit.Location) {
	var b *orbit.BlockLocation
	var directionWrap orbit.Direction
	i := 0
	direction := block.Direction(location, &c.Vision)
	if direction == c.Direction {
		goto Update
	}
	directionWrap = block.Direction(location, &orbit.Location{
		X: c.Vision.X / 2 * 3,
		Y: c.Vision.Y / 2 * 3,
	})
	if directionWrap == c.Direction {
		goto Update
	}
	c.Direction = direction

	if c.NearBlock[i] == nil {
		b := orbit.LocationCM.BlockLocationPool.Get().(*orbit.BlockLocation)
		b.Copy(block)
	} else {
		c.NearBlock[i].Copy(block)
	}
	for d, p := range orbit.DirectionBlockMap {
		if direction&d > 0 {
			i++
			if c.NearBlock[i] == nil {
				b = orbit.LocationCM.BlockLocationPool.Get().(*orbit.BlockLocation)
			} else {
				b = c.NearBlock[i]
			}
			block.Offset(p[0], p[1], b)
			c.NearBlock[i] = b
		}
	}
	for ; i < len(c.NearBlock); i++ {
		if c.NearBlock[i] != nil {
			orbit.LocationCM.ReleaseBlock(c.NearBlock[i])
		}
		c.NearBlock[i] = nil
	}
	orbit.LocationCM.ReleaseBlock(b)
	c.UpdateBlock(mode, container)
Update:
	c.ToUpdateLocation(location)
}

func (c *Client) UpdateBlock(mode ModeService, container cluster.Container) {
	if len(c.BlockSet) == 0 {
		for index, block := range c.NearBlock {
			client := mode.GetWithBlock(c, container, block)
			client.Join(c, c.OpenId)
			c.BlockSet[client.ID()] = client
			if index == 0 {
				c.CurrentClient = client
			}
		}
	} else {
		s := make(map[string]int, len(c.NearBlock))
		i := make(map[interface{}]struct{})
		for index, block := range c.NearBlock {
			client := mode.GetWithBlock(c, container, block)
			if index == 0 {
				c.CurrentClient = client
			}
			if _, ok := c.BlockSet[client.ID()]; !ok {
				client.Join(c, c.OpenId)
				c.BlockSet[client.ID()] = client
			}
			s[client.ID()] = index
		}
		for key, _ := range c.BlockSet {
			if _, ok := s[key]; !ok {
				c.BlockSet[key].Leave(c, c.OpenId)
				delete(c.BlockSet, key)
			}
		}
		// 删除离开的block中的item
		b := serialize.GetBag()
		c.BlockMap.Range(func(key, value interface{}) bool {
			client := value.(*cluster.Client)
			id := client.ID()

			if _, ok := s[id]; !ok {
				c.BlockMap.Delete(key)
				i[key] = struct{}{}
				b.Write(0, key)
				c.WriteDestroy(b.Get())
			}
			return true
		})
		c.AgentMap.Range(func(key, value interface{}) bool {
			if _, ok := i[key]; !ok {
				c.AgentMap.Delete(key)
			}
			if value == nil {
				return true
			}
			block := value.(*orbit.BlockLocation)
			orbit.LocationCM.ReleaseBlock(block)
			return true
		})
		b.Release()
	}
}
